package stud.mpgame.network;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;

/**
 * Сообщение, передаваемое по сети.
 * Содержит в себе набор {@link UserObject} и служебную информацию.
 *
 * @author f.pavel91@gmail.com
 * @version 1.4
 */
public class Message {

    private MessageDescription messageDescription;
    private int serialNumber;
    private int sentTime;
    private int receivedTime;
    private int protocolVersion;
    private List< IoSession > address = new ArrayList< IoSession >();
    private Map< Integer, UserObject > body = new HashMap< Integer, UserObject >();

    /**
     * @param sessions список сессий, которым предназначается сообщение.
     * @param messageDescription тип сообщения.
     */
    public Message(List< IoSession > sessions, MessageDescription messageDescription) {
        this.address = sessions;
        this.messageDescription = messageDescription;
    }

    /**
     *
     * @param session сессия, которой предназначается сообщение.
     * @param messageDescription тип сообщения.
     */
    public Message(IoSession session, MessageDescription messageDescription) {
        this.address.add(session);
        this.messageDescription = messageDescription;
    }

    /**
     *
     * @return Тип сообщения
     */
    public MessageDescription getMessageDescription() {
        return messageDescription;
    }

    /**
     *
     * @return Список сессий - получателей сообщения.
     */
    public List< IoSession > getAddress() {
        return address;
    }

    /**
     *
     * @return Первая сессия из списка получателей.
     */
    public IoSession getFirstSession() {
        return address.get(0);
    }

    /**
     *
     * @return Серийный номер сообщения.
     */
    public int getSerialNumber() {
        return serialNumber;
    }

    /**
     *
     * @return Время отправки сообщения.
     */
    public int getSentTime() {
        return sentTime;
    }

    /**
     *
     * @return Время получения сообщения.
     */
    public int getReceivedTime() {
        //TODO throw exception
        return receivedTime;
    }

    /**
     *
     * @return Версия протокола приема/отправки сообщений.
     */
    int getProtocolVersion() {
        return protocolVersion;
    }

    /**
     *
     * @param userObjectClass объект Class
     * @return {@link UserObject}, соответствующий данному классу.
     */
    public UserObject getUserObject(Class userObjectClass) {
        return body.get(messageDescription.getUserObjectId(userObjectClass));
    }

    /**
     *
     * @param id идентификатор {@link UserObject}.
     * @return {@link UserObject}, соответствующий данному классу.
     */
    public UserObject getUserObject(int id) {
        return body.get(id);
    }

    /**
     * Включение userObject в состав данного message.
     *
     * @param userObject включаемый {@link UserObject}.
     */
    public void putUserObject(UserObject userObject) {
        body.put(messageDescription.getUserObjectId(userObject.getClass()), userObject);
    }

    /**
     * Исключение userObject из состава данного message.
     *
     * @param userObject исключаемый {@link UserObject}.
     */
    public void removeUserObject(UserObject userObject) {
        body.remove(messageDescription.getUserObjectId(userObject.getClass()));
    }

    void writeExternal(IoBuffer ioBuffer, int serialNumber, int sentTime, int protocolVersion) {
        ioBuffer.putInt(messageDescription.getID());
        this.serialNumber = serialNumber;
        ioBuffer.putInt(serialNumber);
        this.sentTime = sentTime;
        ioBuffer.putInt(sentTime);
        this.protocolVersion = protocolVersion;
        ioBuffer.putInt(protocolVersion);
        Set< Integer > identifiers = body.keySet();
        for(int id : identifiers) {
            ioBuffer.putInt(id);
            UserObject userObject = this.getUserObject(id);
            userObject.writeExternal(ioBuffer, this);
        }
    }

    Message(IoBuffer ioBuffer, IoSession ioSession, int receivedTime) throws Exception {
        this.address.add(ioSession);
        int descriptionID = ioBuffer.getInt();
        this.messageDescription = MessageDescription.getMessageDescription(descriptionID);
        this.serialNumber = ioBuffer.getInt();
        this.sentTime = ioBuffer.getInt();
        this.receivedTime = receivedTime;
        this.protocolVersion = ioBuffer.getInt();
        for(int i = 0; i < messageDescription.getIdentifiers().size(); i++) {
            int userObjectId = ioBuffer.getInt();
            UserObject userObject = messageDescription.createUserObject(userObjectId);
            userObject.readExternal(ioBuffer, this);
            this.putUserObject(userObject);
        }
    }

    /**
     * Рассылка сообщения всем адресатам.
     */
    public void send() {
        for(IoSession session : address)
            session.write(this);
    }

    public String toString() {
        String result = "\n";
        result += "MessageDescription " + messageDescription + "\n";
        result += "Serial number " + serialNumber + "\n";
        result += "Sent time " + sentTime + "\n";
        result += "Received time " + receivedTime + "\n";
        result += "Protocol version " + protocolVersion + "\n";
        result += "User objects: ";
        for(int id : body.keySet()) {
            result += "   " + getUserObject(id);
        }
        return result;
    }

}